package com.enerccio.commonJ.objects;

import com.enerccio.commonJ.tools.SpecialVariable;
import com.enerccio.commonJ.utils.Setfable;

/**
 * Implementation of the lisp cons cell, ie an object which holds two objects, 
 * car (first object) and cdr (second object). The names come from the cons function in lisp
 * and registers in list machine (car -> contents of address part of register, 
 * cdr -> contents of decrement part of register).
 * @author enerccio
 *
 */
public final class ConsCell extends commonJObject {
	
	/**
	 * This variable is used to preserve printer to behave correct 
	 * independent of the thread and basically simulates passing by stack value 
	 * of boolean.
	 */
	private static SpecialVariable<Boolean> inPrint = 
			new SpecialVariable<Boolean>(false);
	
	/**
	 * NIL value, one and only
	 */
	public static ConsCell NIL = new ConsCell();
	
	/**
	 * Creates list from provided arguments
	 * @param args objects to be put into the created list
	 * @return newly formed list
	 */
	public static ConsCell list(Object... args){
		if (args.length == 0)
			return NIL;
		ConsCell return_value = new ConsCell();
		ConsCell current = return_value;
		for (int i=0; i<args.length; i++){
			current.setfCar(args[i]);
			if (i == args.length-1)
				current.setfCdr(NIL);
			else{
				current.setfCdr(new ConsCell());
				current = (ConsCell) current.cdr();
			}
		}
		return return_value;
	}
	
	/**
	 * Creates list of n items of NIL
	 * @param n length of the list
	 * @return newly formed list
	 */
	public static ConsCell list(int n){
		return list(n, NIL);
	}
	
	/**
	 * Creates list from N * object 
	 * @param n length of the list
	 * @param arg object to be copied into the list
	 * @return newly formed list
	 */
	public static ConsCell list(int n, Object arg){
		Object[] array = new Object[n];
		for (int i=0; i<n; i++)
			array[i] = arg;
		return list(array);
	}
	
	/** Car value of the cons cell */
	private Object carv;
	/** Cdr value of the cons cell */
	private Object cdrv;
	
	/**
	 * Creates super cell, pointing to itself
	 */
	private ConsCell(){
		carv = this;
		cdrv = this;
	}
	
	/**
	 * Creates the cons cell containing car and cdr objects
	 * @param car value
 	 * @param cdr value
	 */
	public ConsCell(Object car, Object cdr){
		this.carv = car;
		this.cdrv = cdr;
	}
	
	/**
	 * @return car of the cons cell
	 */
	@Setfable
	public synchronized Object car(){
		return carv;
	}
	
	/**
	 * Sets the car of the cons cell
	 * @param o new value of car
	 */
	public synchronized void setfCar(Object o){
		carv = o;
	}
	
	/**
	 * @return cdr of the cons cell
	 */
	@Setfable
	public synchronized Object cdr(){
		return cdrv;
	}
	
	/**
	 * Sets the cdr of the cons cell
	 * @param o new value of cdr
	 */
	public synchronized void setfCdr(Object o){
		cdrv = o;
	}	
	
	/**
	 * Tests whether cons cell is a list. Cons cell is a list if it is NIL or 
	 * if cdr of cons cell is a list.
	 * @return is this cons cell a list?
	 */
	public synchronized boolean isList(){
		if (this == NIL)
			return true;
		if (!(cdrv instanceof ConsCell))
			return false;
		return ((ConsCell) cdrv).isList();
	}
	
	/**
	 * Prints the cons cell and all its content.
	 * Uses thread special variable inPrint.
	 * @return
	 */
	@Override
	public String toString(){
		if (this == NIL)
			return "NIL";
		
		boolean previousV = inPrint.get();
		
		StringBuilder builder = new StringBuilder();
		
		if (!inPrint.get()){
			builder.append("(");
			inPrint.set(true);
		}
		
		boolean oldV = inPrint.get();
		inPrint.set(false);
		builder.append(carv.toString());
		inPrint.set(oldV);
		
		if (cdrv == NIL){
			if (inPrint.get() == true)
				builder.append(")");
		} else if (!(cdrv instanceof ConsCell)){
			builder.append(" . ");
			inPrint.set(false);
			builder.append(cdrv.toString());
			builder.append(")");
		} else {
			builder.append(" ");
			builder.append(cdrv.toString());
		}
		
		inPrint.set(previousV);
		return builder.toString();
	}

}
